﻿//------------------------------------------------------------------------------
// <copyright file="FindCommands.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

using System;
using System.Diagnostics;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.ComponentModel.Design;
using ModelingAddIns;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
using System.Text.RegularExpressions;
//using Microsoft.VisualStudio.Modeling.ClassDesigner.PresentationModel;

[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "DesignToolsEnhancements.FindCommandBase.FindDiagramItem(Microsoft.VisualStudio.Modeling.Diagrams.DiagramItem,Microsoft.VisualStudio.Modeling.Diagrams.DiagramItem&,Microsoft.VisualStudio.Modeling.Diagrams.DiagramItem&):System.Void")]

namespace ModelingAddIns
{
    #region Command classes

    internal static class StandardFindCommands
    {
        private static readonly Guid StandardCommandSet97 = new Guid("5EFC7975-14BC-11CF-9B2B-00AA00573819");

        internal static readonly CommandID FindCommandId = new CommandID(StandardCommandSet97, 97);
        internal static readonly CommandID FindNextCommandId = new CommandID(StandardCommandSet97, 370);
    }

    [Command(Name = "Find Command", Container = typeof(DiagramContext),
        Description = "Enables Find command on the diagram which allows to search for a shape containing certain text.")]
    internal sealed class FindCommand : FindCommandBase
    {
        public FindCommand() : base(StandardFindCommands.FindCommandId) { }
    }

    [Command(Name = "Find Next Command", Container = typeof(DiagramContext),
        Description = "Enables Find Next command on the diagram which allows to search for next shape satisfying previous search criteria.")]
    internal sealed class FindNextCommand : FindCommandBase
    {
        public FindNextCommand() : base(StandardFindCommands.FindNextCommandId) { }
    }

    #endregion

    internal abstract class FindCommandBase : DiagramCommand
    {
        #region Private data, constructor

        private static FindInDiagramForm s_findForm;
        private static DiagramItem s_lastFindResult;

        protected FindCommandBase(CommandID commandId) : base(commandId) { }

        protected override void Dispose(bool disposing)
        {
            try
            {
                s_findForm = null;
                s_lastFindResult = null;
            }
            finally
            {
                base.Dispose(disposing);
            }
        }

        #endregion

        #region Base properties overrides

        protected override bool RequiresTransaction
        {
            get { return false; }
        }

        protected override bool UseWaitCursor
        {
            get { return false; }
        }

        #endregion

        #region History Search logic

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1807:AvoidUnnecessaryStringCreation", MessageId = "text")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1807:AvoidUnnecessaryStringCreation", MessageId = "elementText")]
        private static bool Match(string elementText)
        {
            Debug.Assert(elementText != null);
            Debug.Assert(s_findForm != null);

            string text = s_findForm.findText.Text;

            

            if (!s_findForm.matchCaseCheckBox.Checked)
            {
                text = text.ToUpperInvariant();
                elementText = elementText.ToUpperInvariant();
            }

            if (s_findForm.matchWholeWordCheckBox.Checked)
            {
                elementText = Regex.Replace(elementText, " <CLASSSHAPE>", "");
                return String.Equals(text, elementText, StringComparison.InvariantCulture);
            }
            else
            {
                return elementText.Contains(text);
            }
        }

        private void FindDiagramItem(ShapeElement shape, ref DiagramItem nextAfter, ref DiagramItem result)
        {
            Debug.Assert(shape != null);
            if (shape != null)
            {
                FindDiagramItem(new DiagramItem(shape), ref nextAfter, ref result);
            }
        }

        private void FindDiagramItem(DiagramItem item, ref DiagramItem nextAfter, ref DiagramItem result)
        {
            Debug.Assert(item != null);

            if (!(item.CanSelect && item.CanFocus))
                return;

            // ignore lollipop shapes
            //if (item.Shape is LollipopShape)
                //return;

            // ignore compartments
            if (!(item.Shape is ElementListCompartment && item.SubField == null) && !(item.Shape is Diagram))
            {
                // traverse fields inside compartments
                foreach (ModelElement elem in item.RepresentedElements)
                {
                    if (Match(elem.ToString()))
                    {
                        if (nextAfter == null)
                        {
                            result = item;
                            return;
                        }
                        else
                        {
                            if (item.Shape == nextAfter.Shape && item.Field == nextAfter.Field)
                            {
                                ListItemSubField s1 = nextAfter.SubField as ListItemSubField;
                                ListItemSubField s2 = item.SubField as ListItemSubField;
                                if (s1 == null && s2 == null ||
                                    s1 != null && s2 != null && s1.Row == s2.Row)
                                {
                                    nextAfter = null;
                                }
                            }
                        }
                    }
                }
            }

            // ignore collapsed shapes contents if told to do so
            NodeShape nodeShape = item.Shape as NodeShape;
            if (nodeShape != null && !s_findForm.searchHiddenItemsCheckBox.Checked && !nodeShape.IsExpanded)
                return;

            // traverse nested shapes and fields on shapes
            if (item.Field == null && item.SubField == null)
            {
                foreach (ShapeElement shape in item.Shape.NestedChildShapes)
                {
                    FindDiagramItem(shape, ref nextAfter, ref result);
                    if (result != null) return;
                }
                foreach (ShapeElement shape in item.Shape.RelativeChildShapes)
                {
                    FindDiagramItem(shape, ref nextAfter, ref result);
                    if (result != null) return;
                }
                if (s_findForm.searchMembersCheckBox.Checked)
                {
                    foreach (ShapeField field in item.Shape.ShapeFields)
                    {
                        Microsoft.VisualStudio.Modeling.Diagrams.ListField listField = field as Microsoft.VisualStudio.Modeling.Diagrams.ListField;
                        if (listField != null)
                        {
                            IListFieldContainer container = item.Shape as IListFieldContainer;
                            Debug.Assert(container != null);
                            if (container != null)
                            {
                                for (int i = 0; i < container.GetItemCount(listField); i++)
                                {
                                    FindDiagramItem(new DiagramItem(item.Shape, field, new ListItemSubField(i)), ref nextAfter, ref result);
                                    if (result != null) return;
                                }
                            }
                        }
                        else
                        {
                            FindDiagramItem(new DiagramItem(item.Shape, field), ref nextAfter, ref result);
                            if (result != null) return;
                        }
                    }
                }
            }
        }

        #endregion

        #region OnInvoke method

        private void ExpandParents(ShapeElement shape)
        {
            NodeShape node = shape as NodeShape;
            if (node != null)
            {
                ExpandParents(node.ParentShape);
                if (!node.IsExpanded)
                {
                    Debug.Assert(shape.Store.TransactionManager.InTransaction);
                    node.IsExpanded = true;
                }
            }
        }

        protected override void OnInvoke()
        {
            if (s_findForm == null)
                s_findForm = new FindInDiagramForm(this.ServiceProvider);

            IUIService uiService = this.ServiceProvider.GetService<IUIService>();
            Debug.Assert(uiService != null);
            if ((this is FindNextCommand && s_lastFindResult != null) ||
                uiService != null && uiService.ShowDialog(s_findForm) == DialogResult.OK)
            {
                DiagramItem diagramItem = null;
                if (this is FindCommand)
                {
                    s_lastFindResult = null;
                    FindDiagramItem(Diagram, ref s_lastFindResult, ref diagramItem);
                }
                else
                {
                    Debug.Assert(s_lastFindResult != null);
                    FindDiagramItem(Diagram, ref s_lastFindResult, ref diagramItem);
                    if (diagramItem == null) // wrap around on last item
                    {
                        s_lastFindResult = null;
                        FindDiagramItem(Diagram, ref s_lastFindResult, ref diagramItem);
                    }
                }
                s_lastFindResult = diagramItem;

                if (diagramItem != null)
                {
                    // make sure all collapsed parents are expanded
                    using (Transaction t = Store.TransactionManager.BeginTransaction("Expand Shapes"))
                    {
                        ExpandParents(diagramItem.Field == null ? diagramItem.Shape.ParentShape : diagramItem.Shape);
                        if (t.IsActive && t.HasPendingChanges)
                            t.Commit();
                    }

                    DiagramClientView.EnsureVisible(diagramItem.Shape.AbsoluteBoundingBox);
                    DiagramView.Selection.Set(diagramItem);
                    SetStatusText("Find: " + s_findForm.findText.Text, false);
                }
                else
                {
                    SetStatusText("Element with specified name was not found on the diagram.", true);
                }
            }
        }

        #endregion
    }
}
